﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Neat;
using Neat.Components;
using Neat.Mathematics;
using Neat.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Sectors
{
    public class World
    {
        public string Name;
        //public TimeSpan RunningTime;
        public TimeSpan StartTime = TimeSpan.Zero;
        public string MapName;

        public List<Viewport> ViewPorts;

        public Queue<string> Messages = new Queue<string>();

        public float DefaultFloorHeight = -20;
        public float DefaultCeilingHeight = 20;

        public Dictionary<String, String> Scripts = new Dictionary<string, string>()
        {
            {"Update",null},
            {"OnCreate",null},
            {"OnBegin",null},
            {"OnDeath",null},
            {"OnDestroy",null}
        };

        //TODO: public PhysicsSimulator Simulator;
        public List<Sector> Sectors;
        public List<Wall> Walls;
        //TODO: public List<Entity> Entities;
        //TODO: public List<Projectile> Projectiles;
        //TODO: public List<Player> Players;

        NeatGame game;
        BasicEffect effect;
        PerspectiveCamera fpscam,camera,topcam;
        LineSegment player;

        public FillMode FillMode = FillMode.Solid;
        public CullMode CullMode = CullMode.CullCounterClockwiseFace;

        public World()
        {
            Initialize();
        }

        public World(NeatGame game)
        {
            this.game = game;
            Initialize();
        }

        public World(NeatGame game, World zombieWorld)
        {
            
            this.game = game;
            Initialize();
            Sectors = zombieWorld.Sectors;
            Walls = zombieWorld.Walls;

            for (int i = 0; i < Sectors.Count; i++)
            {
                var item = Sectors[i];
                item.SetParent(game, this);
                item.CreateModel();
            }

            foreach (var item in Walls)
            {
                item.CreateModel();
            }
        }

        public virtual void Initialize()
        {
            Name = "generic";
            MapName = "generic";

            Sectors = new List<Sector>();
            Walls = new List<Wall>();
            if (this.game != null)
            {
                effect = new BasicEffect(game.GraphicsDevice);
                effect.World = Matrix.Identity;
                effect.TextureEnabled = true;
                effect.LightingEnabled = true;
                effect.EnableDefaultLighting();
                fpscam = new PerspectiveCamera(60.0f, game.GraphicsDevice.Viewport.AspectRatio,
                    0.1f, 10000.0f, Vector3.Zero, Vector3.UnitX, Vector3.Up);
                topcam = new TopDownCamera(60.0f, game.GraphicsDevice.Viewport.AspectRatio,
                    0.1f, 10000.0f, new Vector3(0, 1000, 1), Vector3.Zero, new Vector3(0, 0, 1));
                player = new LineSegment(new Vector2(0), 0);
                camera = fpscam;
                camera.Switch(effect);
                AttachToConsole(game.Console);

                game.AutoClear = false;
                game.AutoDraw = false;
            }
        }

        public virtual void HandleInput(GameTime gameTime)
        {
            if (game.IsPressed(Keys.Up, Keys.W)) camera.Forward(4); // player.Forward(2);
            if (game.IsPressed(Keys.Down, Keys.S)) camera.Forward(-4); // player.Forward(-2);
            if (game.IsPressed(Keys.Left)) camera.Turn(MathHelper.ToRadians(3)); // player.Turn(MathHelper.ToRadians(2));
            if (game.IsPressed(Keys.Right)) camera.Turn(MathHelper.ToRadians(-3));// player.Turn(MathHelper.ToRadians(-2));
            if (game.IsPressed(Keys.A)) camera.Strafe(4);
            if (game.IsPressed(Keys.D)) camera.Strafe(-4);
            if (game.IsPressed(Keys.LeftShift)) camera.Elevate(4);
            if (game.IsPressed(Keys.LeftControl)) camera.Elevate(-4);
            if (game.IsPressed(Keys.OemOpenBrackets)) camera = topcam;
            if (game.IsPressed(Keys.OemCloseBrackets)) camera = fpscam;
            camera.Switch(effect);
        }

        protected virtual void Translate(string message)
        {
        }

        public virtual void Update(GameTime gameTime)
        {
            HandleInput(gameTime);
            while (Messages.Count > 0) Translate(Messages.Dequeue());
            //Simulator.Update
            //Entities.Update
            //Projectiles.Update
        }

        public virtual void Render(GameTime gameTime)
        {
            SamplerState ss = new SamplerState();
            ss.Filter = TextureFilter.Anisotropic;

            ss.AddressU = TextureAddressMode.Wrap;
            ss.AddressV = TextureAddressMode.Wrap;

            game.GraphicsDevice.SamplerStates[0] = ss;

            game.GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.Black, 1.0f, 0);
            RasterizerState state = new RasterizerState();

            state.FillMode = this.FillMode;
            state.CullMode = this.CullMode;
            game.GraphicsDevice.RasterizerState = state;
            effect.PreferPerPixelLighting = true;
            foreach (var item in Sectors)
            {
                item.Render(gameTime, effect);
            }
        }

        public void NewSector(String name, Polygon poly)
        {
            Sector s =
                (game != null) ?
                new Sector(game, this, poly, DefaultFloorHeight, DefaultCeilingHeight) :
                new Sector(this, poly, DefaultFloorHeight, DefaultCeilingHeight);

            s.Name = name;
            if (game != null)
                s.AttachToConsole(console);
            Sectors.Add(s);
            FindWalls();
        }

        public void FindWalls()
        {
            foreach (var item in Sectors)
            {
                foreach (var wall in item.Walls)
                {
                    if (GetWall(wall) == null) 
                        Walls.Add(wall);
                }
                //Walls.AddRange(item.Walls);
            }
        }

        public Sector GetSector(string name)
        {
            foreach (var item in Sectors) if (item.Name == name) return item;
            return null;
        }

        public Wall GetWall(LineSegment edge)
        {
            foreach (var item in Walls)
            {
                if ((item.StartPos == edge.StartPos && item.EndPos == edge.EndPos) ||
                    (item.EndPos == edge.StartPos && item.StartPos == edge.EndPos)) return item;
            }
            return null;
        }

        public List<Sector> GetSectors(string name)
        {
            List<Sector> result = new List<Sector>();
            foreach (var item in Sectors) if (item.Name == name) result.Add(item);
            return result;
        }

        #region Console
        Neat.Components.Console console;
        public virtual void AttachToConsole(Neat.Components.Console console)
        {
            this.console = console;
            console.AddCommand("w_name", w_name);
            console.AddCommand("w_mapname", w_mapname);
            console.AddCommand("w_script", w_script);
            console.AddCommand("wc_fov", wc_fov);
            console.AddCommand("wc_near", wc_near);
            console.AddCommand("wc_far", wc_far);
            console.AddCommand("wc_position", wc_position);
            console.AddCommand("wc_target", wc_target);
            console.AddCommand("wc_up", wc_up);
            console.AddCommand("wc_aspectratio", wc_aspectratio);
            console.AddCommand("wc_wireframe", wc_wireframe);
            console.AddCommand("wc_switch", wc_switch);
            console.AddCommand("wc_cull", wc_cull);
            console.AddCommand("wc_fps", wc_fps);
            console.AddCommand("w_newsector", w_newsector);
            console.AddCommand("w_selectsector", w_selectsector);
            console.AddCommand("w_deletesector", w_deletesector);
            console.AddCommand("w_defaultfloor", w_defaultfloor);
            console.AddCommand("w_defaultceiling", w_defaultceiling);
            console.AddCommand("w_newwall", w_newwall);
            console.AddCommand("w_reset", w_reset);
            console.AddCommand("w_wallcount", w_wallcount);
            console.AddCommand("w_createallwalls", w_createallwalls);
        }

        void w_name(IList<string> args)
        {
            Name = args[1];
        }

        void w_reset(IList<string> args)
        {
            Initialize();
        }

        void w_mapname(IList<string> args)
        {
            MapName = args[1];
        }

        void w_script(IList<string> args)
        {
            if (Scripts.ContainsKey(args[1])) Scripts[args[1]] = console.Args2Str(args, 2);
            else Scripts.Add(args[1], console.Args2Str(args, 2));
        }
        
        void wc_fov(IList<string> args)
        {
            if (args.Count < 2) 
                console.WriteLine("FOV: " + camera.Fov.ToString() + " degs, " + 
                    MathHelper.ToRadians(camera.Fov).ToString() + " radians");
            else
                camera.Fov = float.Parse(args[1]);
        }

        void wc_far(IList<string> args)
        {
            if (args.Count < 2)
                console.WriteLine("Far: " + camera.FarPlaneDistance.ToString());
            else
                camera.FarPlaneDistance = float.Parse(args[1]);
        }

        void wc_near(IList<string> args)
        {
            if (args.Count < 2)
                console.WriteLine("Near: " + camera.NearPlaneDistance.ToString());
            else
                camera.NearPlaneDistance = float.Parse(args[1]);
        }

        void w_defaultfloor(IList<string> args)
        {
            if (args.Count < 2)
                console.WriteLine("DefaultFloorHeight: " + DefaultFloorHeight.ToString());
            else
                DefaultFloorHeight = float.Parse(args[1]);
        }
        void w_defaultceiling(IList<string> args)
        {
            if (args.Count < 2)
                console.WriteLine("DefaultCeilingHeight: " + DefaultCeilingHeight.ToString());
            else
                DefaultCeilingHeight = float.Parse(args[1]);
        }

        void wc_position(IList<string> args)
        {
            camera.Position = GeometryHelper.String2Vector3(console.Args2Str(args, 1));
        }

        void wc_target(IList<string> args)
        {
            camera.Target = GeometryHelper.String2Vector3(console.Args2Str(args, 1));
        }

        void wc_up(IList<string> args)
        {
            camera.Up = GeometryHelper.String2Vector3(console.Args2Str(args, 1));
        }

        void wc_aspectratio(IList<string> args)
        {
            if (args.Count < 2)
                console.WriteLine("Aspect Ratio: " + camera.AspectRatio.ToString());
            else
                camera.AspectRatio = float.Parse(args[1]);
        }

        void wc_wireframe(IList<string> args)
        {
            if (bool.Parse(args[1]))
            {
                this.FillMode = Microsoft.Xna.Framework.Graphics.FillMode.WireFrame;
            }
            else this.FillMode = Microsoft.Xna.Framework.Graphics.FillMode.Solid;
        }

        void wc_cull(IList<string> args)
        {
            if (args.Count < 2)
            {
                console.WriteLine("CullMode=" + CullMode.ToString());
                return;
            }
            var c = args[1].ToLower();
            if (c == "none" || c == "off") CullMode = Microsoft.Xna.Framework.Graphics.CullMode.None;
            else if (c == "cw") CullMode = Microsoft.Xna.Framework.Graphics.CullMode.CullClockwiseFace;
            else if (c == "ccw") CullMode = Microsoft.Xna.Framework.Graphics.CullMode.CullCounterClockwiseFace;
        }

        void wc_switch(IList<string> args)
        {
            camera.Switch(effect);
        }

        void w_newwall(IList<string> args) //new wall. syntax: w_newwall [name] [start] [end]
        {
            Wall w = new Wall(game, new LineSegment(GeometryHelper.String2Vector(args[2]), GeometryHelper.String2Vector(args[3])), null);
            w.Name = args[1];
            Walls.Add(w);
            w.AttachToConsole(console);
        }

        void w_createallwalls(IList<string> args)
        {
            foreach (var item in Walls)
            {
                item.CreateModel();
            }
        }
        void w_newsector(IList<string> args) //new sector. syntax: w_newsector [name] [vertices]
        {
            List<Vector2> vertices = new List<Vector2>();
            for (int i = 2; i < args.Count; i++)
                vertices.Add(GeometryHelper.String2Vector(args[i]));
            NewSector(args[1], new Polygon(vertices));
        }

        void w_selectsector(IList<string> args)
        {
            var s = GetSector(args[1]);
            if (s != null)
                s.AttachToConsole(console);
            else
                console.WriteLine("Sector " + args[1] + " not found");
        }

        void w_deletesector(IList<string> args)
        {
            //TODO
        }

        void wc_fps(IList<string> args)
        {
            if (bool.Parse(args[1]))
            {
                camera = fpscam;
            }
            else camera = topcam;
        }

        void w_wallcount(IList<string> args)
        {
            console.WriteLine("Walls: " + Walls.Count.ToString());
        }
        #endregion
    }
}
